/**
 * Utility functions and data.
 */
(function(window) {
    
    /**
     * Utility class providing utility methods and enumerations.
     */
    if(window.Utility) {
        throw "Utility class already loaded.";
    }
    else {
        var Utility = {
            
            /**
             * Alert types that can be used with the showAlert() method.
             */
            alertTypes: {
                            "danger": "alert-danger",
                            "warning": "alert-warning",
                            "success": "alert-success",
                            "info": "alert-info"
                        },
            
            /**
             * This serves as an the JavaScript counterpart of the JsonResponseModelStatus enum in the Java app.
             */
            jsonResponse: {
                            SUCCESS: "SUCCESS",
                            EXCEPTION: "EXCEPTION",
                            INVALID: "INVALID"
                        },
            
            /**
             * Show a Twitter Bootstrap alert within the specified parent container.
             * @param parentId ID of the parent container for the new alert.
             * @param alertType Type of alert to display (as specified by Utility.alertTypes).
             * @param isCloseable Indicates if the alert should display a close icon for the user.
             * @param autoHideDuration Set to 0 to disable auto-hide. Any value greater than 0 will be treated as the duration in seconds for which the alert should be shown.
             * @param alertBody Content of the alert.
             */
            showAlert: function(parentId, alertType, isCloseable, autoHideDuration, alertBody) {
                var parentContainer = $("#" + parentId);
                var bsAlert = null;
                var alertClass = null;
                
                // Verify parent container exists
                if(parentContainer == null) {
                    throw "showAlert called with non-existent parentId [" + parentId + "].";
                }
                
                // Verify alertType is valid
                alertClass = Utility.alertTypes[alertType];
                if(alertClass == null) {
                    throw "showAlert called with invalid alertType [" + alertType + "].";
                }
                
                // Build the alert
                bsAlert = $(  "<div/>", 
                             {
                                 "class": "alert " + alertClass + " fade in",
                                 "role": "alert"
                             }
                          );
                
                // If it's closeable, add a close button
                if(isCloseable == true) {
                    var closeButton =   $(  "<button/>",
                                                    {
                                                "class": "close",
                                                "aria-label": "Close",
                                                "type": "button"
                                            }
                                        ).click(
                                            function() {
                                                bsAlert.alert('close');
                                            }
                                        );
                    
                    $(  "<span/>",
                        {
                            "aria-hidden": "true",
                            "text": "\u00D7"
                        }
                    ).appendTo(closeButton);
                    
                    closeButton.appendTo(bsAlert);
                }
                
                // Add the content to the alert
                bsAlert.append(alertBody);
                
                // Add the alert to the parent container
                bsAlert.appendTo(parentContainer);
                
                // If it's auto-hide
                if(autoHideDuration > 0) {
                    setTimeout( 
                            function() {
                                bsAlert.alert('close');
                            },
                            autoHideDuration * 1000    
                    );
                }
            },
            
            activateSidebarNav: function(ulId, path) {
                var ul = $("#" + ulId);
                
                ul.children("LI").each(
                    function() {
                        $(this).children("A").each(
                            function() {
                                if($(this).attr("href") == path) {
                                    $(this).parent("LI").addClass("active");
                                }
                                else {
                                    $(this).parent("LI").removeClass("active");
                                }
                            }
                        )
                    }
                );
            },
            
            /**
             * Cycles through a form to find AngularJS, declarative validation errors.
             * This method will mark these fields as "ng-touched" so our custom CSS applies
             * the .ng-invalid.ng-touched error style to the fields. This method also returns
             * an array containing the name of each erroneous field and either a generic error message
             * or a custom error message if one is provided in the errorMessages array above.
             * @param $scope ngScope for the Controller of the view being displayed.
             * @param formName Name of the form to check for errors.
             * @returns {Array}
             */
            getAngularFormErrors: function($scope, formName) {
            	var ngForm = $scope[formName];
            	var errorMessages = new Array();
            	var fieldName = null;
            	var formField = null;
            	var fieldLabel = null;
    			var errorMessage = null;
    			var errorKey = null;
    			
            	for(fieldName in ngForm) {
            		
            		// Only check our form fields (ignore Angular properties)
            		if(fieldName.charAt(0) != '$') {
            			formField = ngForm[fieldName];
            			
            			// Determine which errors have been identified for the field
            			for(errorKey in formField.$error) {            				
            				if(formField.$error[errorKey]) {
            					// Add the ng-touched class to the erroneous field
            					$("form[name='" + formName + "']").find("*[name='" + fieldName + "']").each(
        	    					function(index){
        	    						$(this).removeClass("ng-untouched").addClass("ng-touched");
        	    					}
        						);
            					
            					// Attempt to identify the label associated with this field;
            					// if one cannot be found, use the field's name as the label.
            					var matchingLabels = $("label[for='" + fieldName + "']");
            					if(matchingLabels.length > 0) {
            						fieldLabel = matchingLabels.first().text();
            					}
            					else {
            						fieldLabel = fieldName;
            					}
            					
            					// Create the error message.
            					
            					if(
        							typeof(Utility.errorMessages[fieldName]) === 'undefined' ||
        							typeof(Utility.errorMessages[fieldName][errorKey]) === 'undefined'
            					){
            						errorMessage = Utility.defaultErrorMessages[errorKey];
            					}
            					else {
            						errorMessage = Utility.errorMessages[fieldName][errorKey];
            					}
            					
            					errorMessages.push({"field": fieldName, "label" : fieldLabel, "error": errorKey, "message": errorMessage});
            				}
            			}
            			
            		}
        		}
            	
            	return errorMessages;
            },
            
            /**
             * Removes the ng-touched class and resets the error flags on erroneous fields.
             * @param $scope
             * @param formName
             */
            resetAngularFormErrors: function($scope, formName) {
            	var ngForm = $scope[formName];
            	ngForm.$setPristine();
            	ngForm.$setUntouched();
            },
            
            /**
             * Map of error messages keyed by field names and error types.
             */
            errorMessages: {
            	"txtFirstName": {
				    "required": "is a required field."
            	},
            	
            	"txtLastName": {
            		"required": "is a required field."
            	},
            	
            	"txtPhone": {
            		"required": "is a required field."
            	},
            	
            	"txtAddress1": {
            		"required": "is a required field."
            	},
            	
            	"txtCity": {
            		"required": "is a required field."
            	},
            	
            	"txtState": {
            		"required": "is a required field.",
            		"maxlength": "has a maximum length of 2."
            	},
            	
            	"txtPassword2": {
            		"passwordMatch": "must match the first Password field."
            	}
            },
            
            /**
             * Map of default error messages.
             */
            defaultErrorMessages: {
            	"required": "must not be left blank."
            }
            
        };
        
        // expose globally
        window.Utility = Utility;
    }
})(window);